#include "..\interface_types.h"
#include "d3d_particleshader.h"
#include "..\d3d_texture.h"
#include "..\d3d_pointsprite.h"
#include "..\d3d_particles.h"
#include "..\d3d.h"
#include "..\d3d_util.h"
#include "..\d3d_mesh.h"
#include "..\..\common\string.h"
#include "..\..\common\def.h"
#include "..\..\common\util.h"
#include "..\..\engine\engine.h"
#include "..\..\engine\memorymanager.h"
#include "..\..\engine\error.h"
#include "..\..\math\math.h"

D3DVERTEXELEMENT9 ParticleDecl[] =
{
	{0,0,D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,0},
	{0,sizeof(float) * 3,D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,0},
	{0,sizeof(float) * 6,D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,1},
	{0,sizeof(float) * 9,D3DDECLTYPE_FLOAT1, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_PSIZE,0},
	{0,sizeof(float) * 10,D3DDECLTYPE_FLOAT1, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,2},
	{0,sizeof(float) * 11,D3DDECLTYPE_FLOAT1, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,3},
	{0,sizeof(float) * 12,D3DDECLTYPE_FLOAT1, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,4},
	{0,sizeof(float) * 13,D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,0},
	D3DDECL_END()
}; 

D3DVERTEXELEMENT9 PointDecl[] =
{
	{0,0,D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,0},
	{0,sizeof(float) * 3,D3DDECLTYPE_FLOAT1, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_PSIZE,0},
	{0,sizeof(float) * 4,D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,0},
	D3DDECL_END()
}; 

/******************************************************************************************************************
 * D3DParticleShader
 ******************************************************************************************************************/

/**************************************
 *
 **************************************/
D3DSpriteShader::D3DSpriteShader(CD3D9Device *pDevice) : D3DShader(pDevice)
{	}

/**************************************
 *
 **************************************/
D3DSpriteShader::~D3DSpriteShader()
{
	Release();
}

/**************************************
 *
 **************************************/
string D3DSpriteShader::GetType() const
{
	return string("D3DSpriteShader"); 
}

/**************************************
 *
 **************************************/
unsigned int D3DSpriteShader::RenderOrder()
{
	return 2;
}

/**************************************
 *
 **************************************/
D3DVERTEXELEMENT9* D3DSpriteShader::GetVertexDeclarationElements()
{
	return PointDecl;
}

/**************************************
 * Constructs an instance buffer for this
 * shader based on the passed sprite data
 **************************************/
HRESULT D3DSpriteShader::ConstructInstanceBuffer()
{
	unsigned int i = 0;
	unsigned int meshCount = 0; 
	unsigned int totalInstanceCount = 0; 
	m_MeshHashInstances.GetDataInfo(&meshCount, &totalInstanceCount); 

	if(meshCount == 0)
		return E_FAIL; 

	LPDIRECT3DDEVICE9 const pDevice = GetDevice()->m_pD3DDevice; 
	unsigned int mvbSize = m_SubmittedVertexCount * Particle::size;
	
	if(m_pMeshVB) 
	{
		m_pMeshVB->Release(); 
		m_pMeshVB = NULL; 
	}
	//Build up VB of all meshes (they share the same vertex declaration)
	if(pDevice->CreateVertexBuffer(mvbSize,0,0, D3DPOOL_DEFAULT,&m_pMeshVB,NULL) != D3D_OK)
	{	
		m_vbInstanceSize = 0;
		m_MeshCount = 0;
		m_TotalInstanceCount = 0;
		return E_FAIL; 
	}

	Particle *pTmpMeshVertex = NULL; 
	if(m_pMeshVB->Lock(0, NULL, (void **) &pTmpMeshVertex, 0) == D3D_OK)
	{
		for(unsigned int i = 0;  i < meshCount;  i++)
		{
			D3DRenderable*	pMesh = m_MeshHashInstances.KeyAtTableIndex(i);
			unsigned int	meshVertexCount = pMesh->getVertexCount();
			memcpy(pTmpMeshVertex, pMesh->getVertices(), meshVertexCount * pMesh->m_ElementSize); 
			pTmpMeshVertex += meshVertexCount; 
		}
	
		m_pMeshVB->Unlock(); 
	}
	else
	{	
		return E_FAIL; 
	}
	return S_OK; 
} 

/**************************************
 * Allows shader implementations to
 * specfiy custom parameters for an effect
 **************************************/
HRESULT D3DSpriteShader::InitalizeParameters(LPD3DXEFFECT pEffect)
{
	if(ISNULL(pEffect))
		return E_FAIL;

	m_pProjectionHandle = pEffect->GetParameterBySemantic( NULL, "PROJECTION" ); 
	if(ISNULL(m_pProjectionHandle)) throw EE_EXCEPTION("");

	m_pViewHandle = pEffect->GetParameterBySemantic( NULL, "VIEW" ); 
	if(ISNULL(m_pViewHandle)) throw EE_EXCEPTION("");

	return S_OK;
}

/**************************************
 * Updates the parameters of the shader
 **************************************/
HRESULT D3DSpriteShader::UpdateParameters(LPD3DXEFFECT pEffect)
{
	if(ISNULL(pEffect))
		return E_FAIL;

	ViewOrrientation* pCamera = GetDevice()->GetViewOrrientation(); 
	pEffect->SetMatrix(m_pProjectionHandle,&pCamera->m_Projection); 
	pEffect->SetMatrix(m_pViewHandle,&pCamera->m_View); 
	pEffect->SetFloat(m_pTimeHandle,CEngine::GetInstance()->GetTimeDuration()); 
	return S_OK;
}

/**************************************
 * Executes the shader which usually results
 * in geometry being Drawn
 **************************************/
void D3DSpriteShader::Execute(const int flags)
{

	if(flags & RENDER_REFRACTED)
		return;

	if(FAILED(ConstructInstanceBuffer()))
		return;
	GetDevice()->SetCurrentVertexDeclaration(m_pVertexDeclaration);
	GetDevice()->GetDevice()-> SetStreamSource(0, m_pMeshVB, 0, Point::size); 
	unsigned int baseVertex = 0;
	for(unsigned int i = 0, j = m_MeshHashInstances.TableKeyCount();  i < j;  i++)
	{
		HashTable<InstanceData, D3DRenderable*>::Pair *pPair = m_MeshHashInstances.GetPair(i); 
		D3DPointSpriteGroup *pPSgrp = (D3DPointSpriteGroup*)pPair->m_Key;
		
		pPSgrp->PreRender();
		unsigned int passes = 0;
		
		m_pEffect->Begin(&passes, 0); 
		for(unsigned int k = 0; k < passes; k++)
		{
			m_pEffect->BeginPass(k); 
			pPSgrp->Render(baseVertex);
			m_pEffect->EndPass(); 
		}
		m_pEffect->End(); 
		pPSgrp->PostRender();
		baseVertex += pPSgrp->getVertexCount();
	}
}

/******************************************************************************************************************
 * D3DScreenSpriteShader
 ******************************************************************************************************************/

/**************************************
 *
 **************************************/
D3DScreenSpriteShader::D3DScreenSpriteShader(CD3D9Device* const pDevice) : D3DSpriteShader(pDevice)
{	}

/**************************************
 * Allows shader implementations to
 * specfiy custom parameters for an effect
 **************************************/
HRESULT D3DScreenSpriteShader::InitalizeParameters(LPD3DXEFFECT pEffect)
{
	if(ISNULL(pEffect))
		return E_FAIL;

	if(ISNULL(m_pVertexDeclaration))
		if(FAILED(GetDevice()->GetDevice()->CreateVertexDeclaration(ParticleDecl, &m_pVertexDeclaration)))
			throw EE_EXCEPTION("CreateVertexDeclaration(..) failed."); 

	return S_OK;
}

/**************************************
 * Updates the parameters of the shader
 **************************************/
HRESULT D3DScreenSpriteShader::UpdateParameters(LPD3DXEFFECT pEffect)
{
	if(ISNULL(pEffect))
		return E_FAIL;

	ViewOrrientation* pCamera = GetDevice()->GetViewOrrientation(); 
	pEffect->SetMatrix(m_pProjectionHandle,&pCamera->m_Projection); 
	pEffect->SetMatrix(m_pViewHandle,&pCamera->m_View); 
	pEffect->SetFloat(m_pTimeHandle,CEngine::GetInstance()->GetTimeDuration()); 
	return S_OK;
}

/**************************************
 *
 **************************************/
string D3DScreenSpriteShader::GetType() const
{
	return string("D3DScreenSpriteShader"); 
}

/******************************************************************************************************************
 * D3DParticleShader
 ******************************************************************************************************************/

/**************************************
 *
 **************************************/
D3DParticleShader::D3DParticleShader(CD3D9Device* const pDevice) : D3DSpriteShader(pDevice)
{	}

/**************************************
 *
 **************************************/
D3DVERTEXELEMENT9* D3DParticleShader::GetVertexDeclarationElements()
{
	return ParticleDecl;
}

/**************************************
 *
 **************************************/
void D3DParticleShader::Execute(const int flags)
{
	if(flags & RENDER_REFRACTED)
		return;

	if(FAILED(ConstructInstanceBuffer()))
		return;
	GetDevice()->SetCurrentVertexDeclaration(m_pVertexDeclaration);
	GetDevice()->GetDevice()-> SetStreamSource(0, m_pMeshVB, 0, Particle::size); 
	unsigned int baseVertex = 0;
	for(unsigned int i = 0, j = m_MeshHashInstances.TableKeyCount();  i < j;  i++)
	{
		HashTable<InstanceData, D3DRenderable*>::Pair *pPair = m_MeshHashInstances.GetPair(i); 
		D3DParticleSystem *pPS = (D3DParticleSystem*)pPair->m_Key;
		Matrix4f t = pPS->GetTransform();
		D3DXMATRIX d;
		memcpy(&d, &t, sizeof(D3DXMATRIX));
		m_pEffect->SetMatrix("g_mWorld", &d);
		pPS->PreRender();
		unsigned int passes = 0;
		m_pEffect->Begin(&passes, 0); 
		for(unsigned int k = 0; k < passes; k++)
		{
			m_pEffect->BeginPass(k); 
			pPS->Render(baseVertex);
			m_pEffect->EndPass(); 
		}
		m_pEffect->End(); 
		pPS->PostRender();
		baseVertex += pPS->getVertexCount();
	}
}

/**************************************
 *
 **************************************/
unsigned int D3DParticleShader::RenderOrder()
{
	return 2;
}

/**************************************
 * Allows shader implementations to
 * specfiy custom parameters for an effect
 **************************************/
HRESULT D3DParticleShader::InitalizeParameters(LPD3DXEFFECT pEffect)
{
	if(ISNULL(pEffect))
		return E_FAIL;

	m_pProjectionHandle = pEffect->GetParameterBySemantic( NULL, "PROJECTION" ); 
	if(ISNULL(m_pProjectionHandle)) throw EE_EXCEPTION("");

	mp_WorldHandle = pEffect->GetParameterBySemantic( NULL, "WORLD" ); 
	if(ISNULL(mp_WorldHandle)) throw EE_EXCEPTION("");

	m_pViewHandle = pEffect->GetParameterBySemantic( NULL, "VIEW" ); 
	if(ISNULL(m_pViewHandle)) throw EE_EXCEPTION("");

	m_pTimeHandle = pEffect->GetParameterBySemantic( NULL, "TIME" ); 
	if(ISNULL(m_pTimeHandle)) throw EE_EXCEPTION("");

	return S_OK;
}

/**************************************
 * Updates the parameters of the shader
 **************************************/
HRESULT D3DParticleShader::UpdateParameters(LPD3DXEFFECT pEffect)
{
	if(ISNULL(pEffect))
		return E_FAIL;

	ViewOrrientation* pCamera = &GetDevice()->m_camera; 
	pEffect->SetMatrix(m_pProjectionHandle,&pCamera->m_Projection); 
	pEffect->SetMatrix(m_pViewHandle,&pCamera->m_View); 
	pEffect->SetMatrix(mp_WorldHandle,&GetDevice()->m_IdentityMatrix); 
	pEffect->SetFloat(m_pTimeHandle,CEngine::GetInstance()->GetTimeDuration()); 
	return S_OK;
}

/**************************************
 *
 **************************************/
string D3DParticleShader::GetType() const
{
	return string("D3DParticleShader"); 
}

